
#include "vnsenvir.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include "opp_ctype.h"
#include "enumstr.h"
#include "appreg.h"
#include "csimplemodule.h"
#include "ccomponenttype.h"
#include "cmessage.h"
#include "args.h"
#include "speedometer.h"
#include "timeutil.h"
#include "stringutil.h"
#include "cconfigoption.h"
#include "cproperties.h"
#include "cproperty.h"

Register_GlobalConfigOptionU(CFGID_VNS_EXTRA_STACK, "vns-extra-stack", "B",  "8KiB", "Specifies the extra amount of stack that is reserved for each activity() simple module when the simulation is run.")
Register_GlobalConfigOption(CFGID_VNS_INTERACTIVE, "vns-interactive", CFG_BOOL,  "false", "Defines what vns should do when the model contains unassigned parameters. In interactive mode, it asks the user. In non-interactive mode (which is more suitable for batch execution), Cmdenv stops with an error.")
Register_GlobalConfigOption(CFGID_OUTPUT_FILE, "vns-output-file", CFG_FILENAME, NULL, "When a filename is specified, vns redirects standard output into the given file. This is especially useful with parallel simulation. See the `fname-append-host' option as well.")
Register_PerRunConfigOption(CFGID_EXPRESS_MODE, "vns-express-mode", CFG_BOOL, "true", "Selects ``normal'' (debug/trace) or ``express'' mode.")
Register_PerRunConfigOption(CFGID_AUTOFLUSH, "vns-autoflush", CFG_BOOL, "false", "Call fflush(stdout) after each event banner or status update; affects both express and normal mode. Turning on autoflush may have a performance penalty, but it can be useful with printf-style debugging for tracking down program crashes.")
Register_PerRunConfigOption(CFGID_MODULE_MESSAGES, "vns-module-messages", CFG_BOOL, "true", "When vns-express-mode=false: turns printing module ev<< output on/off.")
Register_PerRunConfigOption(CFGID_MESSAGE_TRACE, "vns-message-trace", CFG_BOOL, "false", "When vns-express-mode=false: print a line per message sending (by send(),scheduleAt(), etc) and delivery on the standard output.")
Register_PerRunConfigOptionU(CFGID_STATUS_FREQUENCY, "vns-status-frequency", "s", "2s", "When vns-express-mode=true: print status update every n seconds.")
Register_PerRunConfigOption(CFGID_PERFORMANCE_DISPLAY, "vns-performance-display", CFG_BOOL, "true", "When cmdenv-express-mode=true: print detailed performance information. Turning it on results in a 3-line entry printed on each update, containing ev/sec, simsec/sec, ev/simsec, number of messages created/still present/currently scheduled in FES.")

Register_PerObjectConfigOption(CFGID_CMDENV_EV_OUTPUT, "vns-ev-output", KIND_MODULE, CFG_BOOL, "true", "When cmdenv-express-mode=false: whether Cmdenv should print debug messages (ev<<) from the selected modules.")

static char buffer[1024];

VNSEnvir::VNSEnvir() : EnvirBase() {
	flag = new cStaticFlag();
    fout = stdout;
    opt_autoflush = true;
}

VNSEnvir::~VNSEnvir(){
	delete flag;
}

void VNSEnvir::readOptions()
{
    EnvirBase::readOptions();

    opt_extrastack = (size_t) cfg->getAsDouble(CFGID_VNS_EXTRA_STACK);
    opt_outputfile = cfg->getAsFilename(CFGID_OUTPUT_FILE).c_str();

    if (!opt_outputfile.empty())
    {
        processFileName(opt_outputfile);
        ::printf("VNS: redirecting output to file `%s'...\n",opt_outputfile.c_str());
        FILE *out = fopen(opt_outputfile.c_str(), "w");
        if (!out)
            throw cRuntimeError("Cannot open output redirection file `%s'",opt_outputfile.c_str());
        fout = out;
    }
}

void VNSEnvir::readPerRunOptions()
{
    EnvirBase::readPerRunOptions();

    opt_expressmode = cfg->getAsBool(CFGID_EXPRESS_MODE);
    opt_interactive = cfg->getAsBool(CFGID_VNS_INTERACTIVE);
    opt_autoflush = cfg->getAsBool(CFGID_AUTOFLUSH);
    opt_modulemsgs = cfg->getAsBool(CFGID_MODULE_MESSAGES);
    opt_messagetrace = cfg->getAsBool(CFGID_MESSAGE_TRACE);
    opt_perfdisplay = cfg->getAsBool(CFGID_PERFORMANCE_DISPLAY);
}



int VNSEnvir::run(int argc, char *argv[], cConfiguration *config){
    args = new ArgList();
    args->parse(argc, argv, "h?f:u:l:c:r:p:n:x:X:agGv");  //TODO share spec with startup.cc!
    cfg = dynamic_cast<cConfigurationEx *>(config);
    if (simulationRequired())
    {
        if (setup()){
            return 1;
        }
    }
    return 0;
}

void VNSEnvir::onSimulationStart(){

    ::fprintf(fout, "\nPreparing for running configuration %s, run #%d...\n", opt_configname.c_str(), 0);
    ::fflush(fout);

    cfg->activateConfig("General", 0);
    readPerRunOptions();

	// find network
	cModuleType *network = resolveNetwork(opt_network_name.c_str());
	ASSERT(network);
	// set up network
	::fprintf(fout, "Setting up network `%s'...\n", opt_network_name.c_str());

	setupNetwork(network);

	// prepare for simulation run
	::fprintf(fout, "Initializing...\n");

	disable_tracing = opt_expressmode;
	startRun();

	// run the simulation
	::fprintf(fout, "\nRunning simulation...\n");
}


void VNSEnvir::onSimulationStop(){
	::fprintf(fout, "\nCalling finish() at end of Run...\n");
    simulation.callFinish();
    endRun();
    flushLastLine();
}

void VNSEnvir::componentInitBegin(cComponent *component, int stage)
{
    if (!opt_expressmode && component->isEvEnabled())
        ::fprintf(fout, "Initializing %s %s, stage %d\n",
                component->isModule() ? "module" : "channel", component->getFullPath().c_str(), stage);
}

void VNSEnvir::askParameter(cPar *par, bool unassigned)
{
	/*
    bool success = false;
    while (!success)
    {
        cProperties *props = par->getProperties();
        cProperty *prop = props->get("prompt");
        std::string prompt = prop ? prop->getValue(cProperty::DEFAULTKEY) : "";
        std::string reply;

        // ask the user. note: gets() will signal "cancel" by throwing an exception
        if (!prompt.empty())
            reply = this->gets(prompt.c_str(), par->str().c_str());
        else
            // DO NOT change the "Enter parameter" string. The IDE launcher plugin matches
            // against this string for detecting user input
            reply = this->gets((std::string("Enter parameter `")+par->getFullPath()+"' ("+(unassigned?"unassigned":"ask")+"):").c_str(), par->str().c_str());

        try
        {
            par->parse(reply.c_str());
            success = true;
        }
        catch (std::exception& e)
        {
            ev.printfmsg("%s -- please try again.", e.what());
        }
    }
    */
}

void VNSEnvir::putsmsg(const char *s)
{
    ::fprintf(fout, "\n<!> %s\n\n", s);
    ::fflush(fout);
}

void VNSEnvir::sputn(const char *s, int n)
{
    EnvirBase::sputn(s, n);

    if (disable_tracing)
        return;

    cComponent *ctx = simulation.getContext();
    if (!ctx || (opt_modulemsgs && ctx->isEvEnabled()) || simulation.getContextType()==CTX_FINISH)
    {
        ::fwrite(s,1,n,fout);
        if (opt_autoflush)
            ::fflush(fout);
    }
}

cEnvir& VNSEnvir::flush()
{
    ::fflush(fout);
    return *this;
}

std::string VNSEnvir::gets(const char *prompt, const char *defaultReply)
{
    if (!opt_interactive)
    {
        throw cRuntimeError("The simulation wanted to ask a question, set vns-interactive=true to allow it: \"%s\"", prompt);
    }
    else
    {
        ::fprintf(fout, "%s", prompt);
        if (!opp_isempty(defaultReply))
            ::fprintf(fout, "(default: %s) ", defaultReply);
        ::fflush(fout);

        ::fgets(buffer, 512, stdin);
        buffer[strlen(buffer)-1] = '\0'; // chop LF

        if (buffer[0]=='\x1b') // ESC?
           throw cRuntimeError(eCANCEL);

        return std::string(buffer);
    }
}

bool VNSEnvir::askyesno(const char *question)
{
	return true;
	/*
    if (!opt_interactive)
    {
        throw cRuntimeError("Simulation needs user input in non-interactive mode (prompt text: \"%s (y/n)\")", question);
    }
    else
    {
        // should also return -1 (==CANCEL)
        for(;;)
        {
            ::fprintf(fout, "%s (y/n) ", question);
            ::fflush(fout);
            ::fgets(buffer, 512, stdin);
            buffer[strlen(buffer)-1] = '\0'; // chop LF
            if (opp_toupper(buffer[0])=='Y' && !buffer[1])
                return true;
            else if (opp_toupper(buffer[0])=='N' && !buffer[1])
                return false;
            else
                putsmsg("Please type 'y' or 'n'!\n");
        }
    }*/
}


void VNSEnvir::moduleCreated(cModule *mod)
{
    EnvirBase::moduleCreated(mod);

    //mod->setEvEnabled(false);
}

void VNSEnvir::messageSent_OBSOLETE(cMessage *msg, cGate *)
{
    if (!opt_expressmode && opt_messagetrace)
    {
        ::fprintf(fout, "SENT:   %s\n", msg->info().c_str());
        if (opt_autoflush)
            ::fflush(fout);
    }
}

void VNSEnvir::simulationEvent(cMessage *msg)
{
    EnvirBase::simulationEvent(msg);

    if (!opt_expressmode && opt_messagetrace)
    {
        ::fprintf(fout, "DELIVD: %s\n", msg->info().c_str());  //TODO can go out!
        if (opt_autoflush)
            ::fflush(fout);
    }
}

void VNSEnvir::printUISpecificHelp()
{
    ev << "VNS-specific options:\n";
    ev << "  No options.\n";
}

unsigned VNSEnvir::getExtraStackForEnvir() const
{
    return opt_extrastack;
}

